//#include <QtDebug>
#include "MyModel.h"
#include <QAbstractItemModel>

/*
QStringList dataItem::Fields = QStringList()
					<< "task_id"
					<< "owner"
					<< "date_sent"
					<< "priority"
					<< "status"
					<< "manhours"
					<< "date_started"
					<< "date_finished"
					<< "description";
*/

QStringList __fields() {
	return QStringList()
		<< "task_id"
		<< "priority"
		<< "status";
}
QStringList dataItem::Fields = __fields();

QHash<QString, QVariant::Type> __types() {
	QHash<QString, QVariant::Type> ret;
	ret["task_id"] = QVariant::UInt;
	ret["priority"] = QVariant::String;
	ret["status"] = QVariant::String;
//	ret["priority"] = QVariant::UInt;
//	ret["status"] = QVariant::UInt;
/*	ret["description"] = QVariant::String;
	ret["owner"] = QVariant::String;
	ret["date_sent"] = QVariant::DateTime;
	ret["date_started"] = QVariant::DateTime;
	ret["date_finished"] = QVariant::DateTime;
	ret["manhours"] = QVariant::Time;
*/	return ret;
}

QHash<QString, QVariant::Type> dataItem::types = __types();




MyModel::MyModel( QObject * parent, QSqlDatabase db)
		: QAbstractTableModel(parent),
		dbh(db),
		priorityModel(new QSqlTableModel(0,dbh)),
		statusModel(new QSqlTableModel(0,dbh)),
		insertTaskQuery(QSqlQuery(dbh)),
		insertAttrQuery(QSqlQuery(dbh)),
		updateTaskQuery(QSqlQuery(dbh)),
		updateAttrQuery(QSqlQuery(dbh)),
		newStatusQuery(QSqlQuery(dbh)),
		statusQuery(QSqlQuery(dbh)),
		sortOrder(" order by task_id asc")
{
//	connect(this, SIGNAL(beforeUpdate(int, QSqlRecord&)), SLOT(beforeUpdateSlot(int, QSqlRecord&)));

	if (! fillDict(statuses, "select status_id, s_name from status_dict") ) {
		qWarning() << "problem while loading statuses";
		qApp->exit(1);
	}
	if (! fillDict(priorities, "select priority_id, p_name from priority_dict") ) {
		qWarning() << "problem while loading priorities";
		qApp->exit(1);
	}
	updateTaskQuery.prepare("update tasks set priority= :priority, status= :status where task_id = :task_id");
	insertTaskQuery.prepare("insert into tasks (priority, status) values (:priority, :status)");
	updateAttrQuery.prepare("update attributes set value = :value where task_id = :task_id and dict_id = :dict_id");
	insertAttrQuery.prepare("insert into attributes (task_id, dict_id, value) values (:task_id, :dict_id, :value)");
	if (!dictQuery.prepare("select a.*, d.name from attributes a, dict d where d.dict_id = a.dict_id and a.task_id = :task_id")) {
		qWarning() << dictQuery.lastError();
	}
	selectAllFromDb();
	statusQuery.prepare("select * from status_hist where task_id = :task_id order by beg");
	newStatusQuery.prepare("insert into status_hist (task_id, status_id) values (:task_id, :status_id)");
	priorityModel->setTable("priority_dict");
	priorityModel->select();
	statusModel->setTable("status_dict");
	statusModel->select();
}

void MyModel::sort(int c, Qt::SortOrder desc) {
	qDebug() << "sort";
	QString lastOrder = sortOrder;
	sortOrder = " order by " + dataItem::fieldName(c) +
		    (desc ? " desc" : " asc");
	if ( lastOrder == sortOrder )
		return;
	selectAllFromDb();
	emit layoutChanged();
	emit sortColumnChanged(c);
	emit message(tr("Sorted by")+" "+dataItem::fieldName(c));
}

bool MyModel::setData (const QModelIndex &index, const QVariant &value, int role) {
	if ( role != Qt::EditRole )
		return true;
	dataItem &row = allData[index.row()];
	row[index.column()] = value;
	row.convert(index.column());
	if ( row["task_id"] == 0 ) {
		insertRowSql(row);
	} else {
		updateRow(row, index);
	}
	emit dataChanged(index,index);
	return true;
}

bool MyModel::insertRowSql(const dataItem &r/*row*/) {
	qDebug() << "insert row";
	bool allFilled = true;
	QStringList notFilled;
	if ( r["task_id"].toInt() != 0 ) {
		qWarning() << "Too strange: task_id defined. I should die here";
		return false;
	}
	if ( r["priority"].toInt() == 0 ) {
		allFilled = false;
		notFilled << tr("priority");
	}
	if ( r["status"].toInt() == 0 ) {
		allFilled = false;
		notFilled << tr("status");
	}
	foreach (dictItem di, dict) {
		if ( di.mandatory && r[di.attr_name].isNull() ) {
			allFilled = false;
			notFilled << di.attr_name;
		}
	}
	if (!allFilled) {
		emit message(QStringList() << tr("You should now fill fields") << notFilled);
		return true;
	}
	try {
		dbh.transaction();
		if ( bindAndExec(insertTaskQuery, r, QStringList() << "priority" << "status") ) {
			int task_id = insertTaskQuery.lastInsertId().toInt();
			foreach (dictItem di, dict) {
				if ( !r[di.attr_name].isNull() ) {
					QHash<QString, QVariant> tmp;
					tmp["task_id"] = task_id;
					tmp["dict_id"] = di.dict_id;
					tmp["value"] = r[di.attr_name];
					if ( !bindAndExec(insertAttrQuery, tmp, tmp.keys()) ) {
						throw SqlException("", insertAttrQuery.lastError());
					}
				}
			}
			dbh.commit();
			selectAllFromDb();
			return true;
		} else {
			throw SqlException("", insertTaskQuery.lastError());
		}
	} catch (SqlException &e) {
		qWarning() << e.sqlError();
		emit message("Can't create task, sorry");
		dbh.rollback();
		return false;
	}
}

bool MyModel::updateRow(const dataItem &r/*row*/, const QModelIndex &index) {
	qDebug() << "update row" << index;
	bool allFilled = true;
	bool criticalNotFilled = false;
	QStringList notFilled;
	if ( r["task_id"].toInt() == 0 ) {
		qWarning() << "Too strange: task_id not defined. I should die here";
		return false;
	}
	if ( r["priority"].toInt() == 0 ) {
		allFilled = false;
		criticalNotFilled = true;
		notFilled << tr("priority");
	}
	if ( r["status"].toInt() == 0 ) {
		allFilled = false;
		criticalNotFilled = true;
		notFilled << tr("status");
	}
	foreach (dictItem di, dict) {
		if ( di.mandatory && r[di.attr_name].isNull() ) {
			allFilled = false;
			notFilled << di.attr_name;
		}
	}
	if (!allFilled) {
		emit message(QStringList() << tr("Please, now fill fields") << notFilled);
		if (criticalNotFilled)
			return false;
	}
	dbh.transaction();
	QString field = dataItem::fieldName(index.column());
	QHash <QString, QVariant> tmp;
	QSqlQuery tmpQ;
	if (dict.contains(field)) { // it is an attribute
		tmp["dict_id"] = dict[field].dict_id;
		tmp["task_id"] = r["task_id"];
		tmp["value"] = r[field];
		tmpQ = allData[index.row()][field].isNull() ? updateAttrQuery : insertAttrQuery;
	} else { // it's a task itself
		tmp["task_id"] = r["task_id"];
		tmp["status"] = r["status"];
		tmp["priority"] = r["priority"];
		tmpQ = updateTaskQuery;
	}
	if ( bindAndExec(tmpQ, tmp, tmp.keys()) ) {
		dbh.commit();
		return true;
	} else {
		dbh.rollback();
		return false;
	}
	return true;
}

template <class HASH>
bool MyModel::bindAndExec(QSqlQuery &q/*query*/, const HASH &r/*row*/, const QStringList &fields) const {
	foreach (QString field, fields) {
		q.bindValue(":"+field, r[field]);
	}
	if ( !q.exec() ) {
		qWarning() << "can't execute statement";
		qWarning() << q.lastError();
		qDebug() << q.lastQuery();
		qDebug() << fields;
		qDebug() << q.boundValues();
		return false;
	}
	return true;
}

// Empty row
void MyModel::addRow() {
	QAbstractItemModel::beginInsertRows(QModelIndex(), rowCount(), rowCount());
	dataItem i;
	i["status"] = QVariant(1);
	i["task_id"] = QVariant(0);
	i["manhours"] = QVariant(QTime(1,30));
	i["date_sent"] = QVariant(QDateTime::currentDateTime());
	allData.append(i);
	endInsertRows();
	insertRowSql(i);
}

void MyModel::deleteRow(const QModelIndexList &rows) {
	QStringList ids;
	foreach ( const QModelIndex i, rows ) {
		ids << allData[i.row()]["task_id"].toString();
	}
	qDebug() << ids;
	if ( ids.count() > 0 ) {
		QSqlQuery q(dbh);
		QString del = "delete from tasks where task_id in ("+ids.join(",")+")";
		if ( q.exec(del) ) {
			foreach(QString id, ids) {
				allData.removeAt(rowIdByTaskId(id.toUInt()));
			}
		} else {
			qWarning() << "can't delete row";
			qWarning() << q.lastError();
			qDebug() << q.lastQuery();
			return;
		}
	}
	emit layoutChanged();
}

void MyModel::setStartedRows(const QModelIndexList &rows) {
	bool allOk = true;
	QStringList messages;
	foreach ( const QModelIndex i, rows ) {
		dataItem &row = allData[i.row()];
		if ( row["status"].toInt() >= statusId("started") ) {
			allOk = false;
			messages << tr("Task %n is already started", "", row["task_id"].toInt());
			continue;
		}
		allData[i.row()]["status"] = statusId("started");
		updateRow(allData[i.row()], this->index(i.row(), dataItem::indexOf("status")));
		emit dataChanged(index(i.row(), 0), index(i.row(), columnCount()));
	}
	if (!allOk) {
		messages.push_front(tr("Some tasks was not started"));
		emit message(messages);
	} else {
		emit message(tr("Tasks started"));
	}
}


bool MyModel::fillDict(QList<QPair<int,QString> > &dict, const QString &query) {
	dict.clear();
	QSqlQuery q(dbh);
	if (!q.exec(query)) {
		qWarning() << q.lastError();
		return false;
	}
	if ( q.record().count() != 2 ) {
		qWarning() << "ERROR while loading dict: query selects more than two fields";
		return false;
	}
	while(q.next())
		dict.append(QPair<int,QString>(q.value(0).toInt(),q.value(1).toString()));
	return true;
}

bool MyModel::initDict() {
	dict.clear();
	dataItem::types = __types();
	dataItem::Fields = __fields();
	QSqlQuery sth(dbh);
	if ( !sth.exec("select * from dict") ) {
		qWarning() << "While init dict:" << sth.lastError();
		return false;
	}
	while (sth.next()) {
		dictItem item;
		QSqlRecord r = sth.record();
		item.dict_id = r.value("dict_id").toUInt();
		item.type = (QVariant::Type)r.value("type").toInt();
		item.attr_name = r.value("name").toString();
		item.mandatory = r.value("mandatory").toBool();
		item.completion = r.value("completion").toBool();
		dict[item.attr_name] = item;
		dataItem::types[item.attr_name] = item.type;
		dataItem::Fields << item.attr_name;
		if ( item.completion ) {
			QSqlQueryModel *qm = new QSqlQueryModel();
			qm->setQuery("select distinct value from attributes where dict_id = "+QString::number(item.dict_id), dbh);
			autoCompleteModels[item.attr_name] = qm;
		}
	}
	return true;
}

bool MyModel::selectAllFromDb() {
	qDebug() << "Select all from DB";
	allData.clear();
	cacheRowByTaskId.clear();
	if (!initDict()) {
		qWarning() << "can't init dict";
		return false;
	}
	QString query = "select * from tasks " + sortOrder;
	QSqlQuery q(dbh);
	if (!q.exec(query)) {
		qWarning() << q.lastError();
		return false;
	}
	int count = 0;
	while ( q.next() ) {
		dataItem item;
		QSqlRecord rec = q.record();
		for (int field = 0; field < rec.count(); field++) {
			QString f = rec.fieldName(field);
			item[f] = rec.value(field);
			item[f].convert(dataItem::type(f));
		}
		dictQuery.bindValue(":task_id", item["task_id"]);
		if (!dictQuery.exec()) {
			qWarning() << "error while getting attrs:" << dictQuery.lastError();
			qWarning() << dictQuery.lastQuery();
			qWarning() << dictQuery.boundValues();
			return false;
		}
		while ( dictQuery.next() ) {
			rec = dictQuery.record();
			QString f = rec.value("name").toString();
			item[f] = rec.value("value");
			item[f].convert(dataItem::type(f));
		}
		cacheRowByTaskId[item["task_id"].toInt()] = count++;
		allData.append(item);
	}
	return true;
}

QVariant MyModel::data(const QModelIndex &i/*index*/, int role) const {
	dataItem row = allData.at(i.row());
//	qDebug() << row.desc;
	if ( !i.isValid() )
		return QVariant();
	if ( i.row() >= rowCount() || i.column() >= columnCount() )
		return QVariant();
	switch(role) {
		case Qt::EditRole:
			if ( dataItem::type(i.column()) == QVariant::DateTime
				&& row[i.column()].toString().isEmpty() ) {
				return QDateTime::currentDateTime();
			}
			if ( dataItem::type(i.column()) == QVariant::UInt
				&& row[i.column()].toString().isEmpty() )
				return QVariant(0);
			if ( dataItem::type(i.column()) == QVariant::DateTime
			     || dataItem::type(i.column()) == QVariant::Time )
				// In edit role return dateTime, in displayRole
				// (below) return formatted string
				return row[i.column()];
			//
		case Qt::DisplayRole:
		case Qt::ToolTipRole:
			if ( i.column() == dataItem::indexOf("priority"))
				return priority(row[i.column()].toInt());
			else if ( i.column() == dataItem::indexOf("status") )
				return status(row[i.column()].toInt());
			else if ( dataItem::type(i.column()) == QVariant::Time )
				return row[i.column()].toTime().toString("hh:mm");
			else if ( dataItem::type(i.column()) == QVariant::DateTime )
				return row[i.column()].toDateTime().toString("dd.MM.yyyy hh:mm");
			else
				return row[i.column()];
		default:
			return QVariant();
	}
}

QVariant MyModel::headerData (int section, Qt::Orientation orientation, int role) const {
	if (role != Qt::DisplayRole)
		return QVariant();
	if (orientation == Qt::Horizontal ) {
		return dataItem::Fields[section];
	} else {
		return QVariant(section+1);
	}
}

int MyModel::rowCount(const QModelIndex &/*parent*/) const {
	return allData.count();
}

int MyModel::columnCount (const QModelIndex &/*parent*/) const {
	return dataItem::colCount();
}

Qt::ItemFlags MyModel::flags (const QModelIndex &index) const {
	Qt::ItemFlags flags = Qt::ItemFlags() | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
	if ( dataItem::fieldName(index.column()) == "task_id" ) {
		return flags;
	}
	flags |= Qt::ItemIsEditable;
	return flags;
}

QSqlQueryModel *MyModel::relationModel(const int column) const {
	if (column == dataItem::indexOf("priority")) {
		return priorityModel;
	}
	else if ( column == dataItem::indexOf("status") ) {
		return statusModel;
	}
	else if ( autoCompleteModels.contains(dataItem::fieldName(column)) ) {
		return autoCompleteModels[dataItem::fieldName(column)];
	}
	else
		return 0;
}

int MyModel::relationIndex(const int column) const {
	if (column == dataItem::indexOf("priority") || column == dataItem::indexOf("status") ) {
		return 1;
	}
	else if ( autoCompleteModels.contains(dataItem::fieldName(column)) ) {
		return 0;
	}
	else
		return -1;
}

int MyModel::rowIdByTaskId(const int id) const {
	return cacheRowByTaskId.value(id,-1);
}


